package com.wzx.core.ioc.context;

import com.wzx.core.ioc.anootaction.ComponentScan;
import com.wzx.core.ioc.bean.BeanDefinition;
import com.wzx.core.ioc.bean.ObjectFactory;
import com.wzx.core.utils.ClassUtil;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AnnotationApplicationContext extends AbstractApplicationContext implements BeanFactory {

    private Class<?> configurationClass;

    private boolean exposeEarlyBean = true;


    /**
     * 需要解析的Class, 通过文件加载的Class
     */
    private List<BeanDefinition> needResolveComponentClassList = new ArrayList<>();

    /**
     * 1缓存
     */
    private Map<String, Object> singletonObjects = new HashMap<>();
    /**
     * 2缓存
     */
    private Map<String, Object> earlySingletonObjects = new HashMap<>();
    /**
     * 3缓存
     */
    private Map<String, ObjectFactory> singletonFactory = new HashMap<>();


    public AnnotationApplicationContext(Class<?> configurationClass) throws Exception {
        this.configurationClass = configurationClass;
        this.init();
    }

    private void init() throws Exception {

        // 初始化后置处理器
        this.initPostBeanProcess();

        // 第一步扫描包，获取需要解析的类
        this.resolveComponentScan();

        // 第二部解析类，初始化到实例工厂
        this.resolveClass();
    }

    private void resolveClass() throws Exception {
        for (BeanDefinition beanDefinition : this.needResolveComponentClassList) {
            this.createBean(beanDefinition);
        }
    }

    /**
     *
     */
    private void createBean(BeanDefinition bd) {
        try {
            Object originBean = getSingleton(bd.getBeanName());

            if (originBean == null) {
                originBean = this.doCreateBean(bd);
            }

            // 默认暴露出来
            Object finalOriginBean = originBean;
            this.addSingletonFactory(bd.getBeanName(), () -> {
                try {
                    return invokePostBeanProcess(bd.getBeanName(), finalOriginBean);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            });

            /**
             * 填充属性
             */
            fillBean(originBean, bd);

            Object singleton = getSingleton(bd.getBeanName(), true);
            if (singleton != null) {
                originBean = singleton;
            }

            this.addSingleton(bd.getBeanName(), originBean);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object invokePostBeanProcess(String beanName, Object bean) {
        for (BeanPostProcess beanPostProcess : BEAN_POST_PROCESS_LIST) {
            Object newBean = beanPostProcess.after(beanName, bean);
            if (newBean != null) {
                bean = newBean;
            }
        }
        return bean;
    }

    private void addSingleton(String beanName, Object originBean) {
        this.singletonObjects.put(beanName, originBean);
        this.singletonFactory.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
    }

    private void fillBean(Object originBean, BeanDefinition bd) throws Exception {
        for (Map.Entry<String, PropertyValue> property : bd.getPropertyMap().entrySet()) {
            Field field = originBean.getClass().getDeclaredField(property.getKey());
            field.setAccessible(true);
            if (property.getValue() instanceof NameValuePropertyValue) {
                Object value = ((NameValuePropertyValue) property.getValue()).getValue();
                field.set(originBean, value);
            } else if (property.getValue() instanceof ReferPropertyValue) {
                String refBeanName = ((ReferPropertyValue) property.getValue()).getRefBeanName();
                Object value = this.getBean(refBeanName);
                field.set(originBean, value);
            } else {
                throw new RuntimeException("未知的类型");
            }
        }
    }

    private void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
        if (!this.singletonFactory.containsKey(beanName)) {
            this.singletonFactory.put(beanName, singletonFactory);
        }
    }

    /**
     * 负责构建原始实例
     */
    private Object doCreateBean(BeanDefinition bd) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (bd.isManualBean()) {
            return bd.getManualBean();
        }

        // 无参构造
        Constructor constructor = bd.getOriginClass().getConstructor();
        return constructor.newInstance();
    }


    /**
     * 扫描包
     */
    private void resolveComponentScan() {
        ComponentScan scan = this.configurationClass.getAnnotation(ComponentScan.class);
        for (String packStr : scan.value()) {
            List<BeanDefinition> classList = ClassUtil.getAllClassForBeanDefinition(packStr);
            needResolveComponentClassList.addAll(classList);
        }
    }

    @Override
    public <T> T getBean(String beanName) {
       Object bean = getSingleton(beanName);
       if (bean == null) {
           BeanDefinition beanDefinition = this.needResolveComponentClassList.stream().filter(item -> item.getBeanName().equals(beanName)).findFirst().orElse(null);
           if (beanDefinition == null) {
               throw new RuntimeException("不存在bean: " + beanName);
           }
           this.createBean(beanDefinition);
           return getBean(beanName);
       }
       return (T) bean;
    }

    private <T> T getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }

    private <T> T getSingleton(String beanName, boolean expose) {
        if (this.singletonObjects.containsKey(beanName)) {
            return (T) this.singletonObjects.get(beanName);
        }
        if (this.earlySingletonObjects.containsKey(beanName)) {
            return (T) this.earlySingletonObjects.get(beanName);
        }
        if (expose && this.singletonFactory.containsKey(beanName)) {
            ObjectFactory objectFactory = this.singletonFactory.get(beanName);
            Object object = objectFactory.getObject();
            this.earlySingletonObjects.put(beanName, object);
            this.singletonFactory.remove(beanName);
            return (T) object;
        }
        return null;
    }


    @Override
    public <T> Map<String, T> getBeanTypeForMap(Class cls) {
        return null;
    }

    public List<BeanDefinition> getNeedResolveComponentClassList() {
        return needResolveComponentClassList;
    }
}
